package com.apobates.forum.toro.core;

import com.apobates.forum.toro.utils.BeanHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * 布尔表达式
 */
public final class BoolExpress {
    private final static BeanParser<BoolOperation> parser;
    private final StringExpressionExecutor executor;
    private volatile static BoolExpress instance = null;

    static {
        //GE:type(attr,300)&EQ:type(level,2)
        Express exp = Express.getInstance()
                .word("fun")
                .corner(Corner.sequence(0).edge(":").build())
                .word("type")
                .corner(Corner.sequence(1).edge("(").build())
                .word("varname")
                .corner(Corner.sequence(2).edge(",").build())
                .word("constant")
                .corner(Corner.sequence(3).edge(")").build());
        // 从表达式到实例
        parser = exp.build(BoolOperation.class);
    }

    private BoolExpress(StringExpressionExecutor expressionExecutor) {
        this.executor = expressionExecutor;
    }
    /* Inner class to provide instance of class
    private static class BoolExpressionSingleton {
        private static final BoolExpress INSTANCE = new BoolExpress(new JsExpressionExecutor());
    }
    public static BoolExpress getInstance() {
        return BoolExpressionSingleton.INSTANCE;
    }*/

    /**
     * 使用内置的StringExpressionExecutor实现来初始化
     * @return
     */
    public static BoolExpress getInstance() {
        return getInstance(new JsExpressionExecutor());
    }

    /**
     * 使用指定的StringExpressionExecutor实现来初始化
     * @param executor
     * @return
     */
    public static BoolExpress getInstance(StringExpressionExecutor executor) {
        if (instance == null) {
            instance = new BoolExpress(executor);
        }
        return instance;
    }

    /**
     * 设置计算的自定义表达式
     * @param express
     * @return
     */
    public ExecuteProcess expression(String express){
        return new ExecuteProcess(express);
    }

    private boolean general(Function<String,Object> decideFun, String express){
        Map<String,Boolean> data = new HashMap<>();
        // 分隔子表达式的数组
        String[] segments = StringUtils.replaceEach(
                express,
                new String[]{")|", ")&"},
                new String[]{")|;", ")&;"}).split(";");
        for(String seg:segments){
            // 分解子表达式
            Pair<String,String> temp = this.resolvePartExpress(seg);
            try {
                // 计算子表达式的值
                BoolOperation bov = parser.parse(temp.getRight());
                data.put(temp.getLeft(), this.evaluate(decideFun.apply(bov.getVarname()), bov));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        // 返回最终的结果
        return this.execute(data);
    }

    // key= 下一步的操作符,若不存在使用：=
    // value= 子表达式
    private Pair<String,String> resolvePartExpress(String expSegment) {
        String tmp = expSegment;
        String symbol = "=";
        String lastSymbol = tmp.substring(tmp.length()-1);
        if(BoolLogicType.getAllowSymbol().contains(lastSymbol)){
            symbol = lastSymbol;
            tmp = tmp.substring(0, tmp.length()-1);
        }
        return Pair.of(symbol, tmp);
    }
    // 计算某个子表达式的结果
    private <T> boolean evaluate(Object attrVal, BoolOperation operator) {
        return BoolFunType.getInstance(operator.getFun()).evaluate(
                attrVal,
                operator.getConstant(),
                operator.getType(),
                executor);
    }
    // 计算整个表达式的最终结果
    // key= 下一步的符号(!,|,&,=), value= 布尔值
    private boolean execute(Map<String,Boolean> result){
        BiFunction<String, Map.Entry<String,Boolean>, String> bf = (String curRs, Map.Entry<String,Boolean> entry)-> {
            BoolLogicType blt = BoolLogicType.getInstanceBySymbol(entry.getKey());
            String tmp = entry.getValue().toString();
            return curRs + String.format("%s%s", tmp, (null==blt)?"": " "+blt.getOperator()+" ");
        };
        String bc = result.entrySet().stream().reduce("", bf, String::concat);
        System.out.println("[BE]current expression: "+bc);
        // 格式: true || false
        return executor.execute(bc).map(obj->obj instanceof Boolean?(Boolean)obj:false).orElse(false);
    }

    public class ExecuteProcess{
        private final String express;

        public ExecuteProcess(String express) {
            this.express = express;
        }

        /**
         * 判断指定对象实例是否满足表达式
         * 要求：表达式中的变量名在对象中存在指定的属性
         * @param instance
         * @return
         * @param <T>
         */
        public <T> boolean decide(final T instance) {
            return BoolExpress.this.general((String attrName)->BeanHelper.getAttrVal(instance, attrName), this.express);
        }

        /**
         * 判断参数Map是否满足表达式
         * 要求：表达式中的变量名在Map的keys中存在
         * @param dataMap
         * @return
         */
        public boolean decide(final Map<String,Object> dataMap) {
            return BoolExpress.this.general((String key)->dataMap.get(key), this.express);
        }
    }

    // 子表达式的操作元素
    public static class BoolOperation{
        // 函数名
        private String fun;
        // 变量名称
        private String varname;
        // 常量值
        private String constant;
        // 变量的类型/判断的类型
        private String type;

        public String getFun() {
            return fun;
        }

        public void setFun(String fun) {
            this.fun = fun;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getVarname() {
            return varname;
        }

        public void setVarname(String varname) {
            this.varname = varname;
        }

        public String getConstant() {
            return constant;
        }

        public void setConstant(String constant) {
            this.constant = constant;
        }
    }

}
